// Copyright by LunaSec (owned by Refinery Labs, Inc)
//
// Licensed under the Business Source License v1.1
// (the "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://github.com/lunasec-io/lunasec/blob/master/licenses/BSL-LunaTrace.txt
//
// See the License for the specific language governing permissions and
// limitations under the License.
package vulnerability

import (
	"github.com/rs/zerolog/log"
	"github.com/urfave/cli/v2"
	"go.uber.org/fx"

	"github.com/lunasec-io/lunasec/lunatrace/bsl/ingest-worker/pkg/vulnerability"
	"github.com/lunasec-io/lunasec/lunatrace/bsl/ingest-worker/pkg/vulnerability/affected"
	"github.com/lunasec-io/lunasec/lunatrace/bsl/ingest-worker/pkg/vulnerability/scrape"

	"github.com/ajvpot/clifx"
)

type Params struct {
	fx.In

	Scraper          scrape.Scraper
	Ingester         vulnerability.AdvisoryIngester
	AffectedIngester affected.Ingester
	Processor        vulnerability.Processor
}

func NewCommand(p Params) clifx.CommandResult {
	return clifx.CommandResult{
		Command: &cli.Command{
			Name: "vulnerability",
			Subcommands: []*cli.Command{
				{
					Name:        "process",
					Description: "Process a file or directory containing vulnerabilities. All references are crawled and their content is downloaded.",
					Flags:       []cli.Flag{},
					Subcommands: []*cli.Command{
						{
							Name: "scrape",
							Flags: []cli.Flag{
								&cli.StringFlag{
									Name:     "ecosystem",
									Usage:    "Scrape specific package ecosystem.",
									Required: false,
								},
								&cli.StringFlag{
									Name:     "vuln",
									Usage:    "Scrape specific vulnerability.",
									Required: false,
								},
								&cli.StringFlag{
									Name:     "url",
									Usage:    "Scrape a specific url and print response.",
									Required: false,
								},
								&cli.BoolFlag{
									Name:     "only-unfetched",
									Usage:    "Scrape only unfetched content.",
									Required: false,
								},
							},
							Action: func(ctx *cli.Context) error {
								vulnId := ctx.String("vuln")
								ecosystem := ctx.String("ecosystem")
								onlyUnfetched := ctx.Bool("only-unfetched")
								url := ctx.String("url")
								// Just scrape one page to stdout if url is specified
								if url != "" {
									resp, err := p.Scraper.ScrapeURLWithChrome(url)
									if err != nil {
										return err
									}
									println(resp.Content)
									return nil
								}
								return p.Scraper.ScrapeVulnerabilities(ecosystem, vulnId, onlyUnfetched)
							},
						},
						{
							Name: "embedding",
							Flags: []cli.Flag{
								&cli.StringFlag{
									Name:     "vuln",
									Usage:    "Cache specific vulnerability.",
									Required: false,
								},
							},
							Action: func(ctx *cli.Context) error {
								vuln := ctx.String("vuln")
								return p.Processor.GenerateEmbeddingsForReferences(vuln)
							},
						},
						{
							Name: "export",
							Flags: []cli.Flag{
								&cli.BoolFlag{
									Name:     "markdown",
									Usage:    "Output in markdown format. Default is json.",
									Required: false,
								},
							},
							Usage: "[vulnerability id]",
							Action: func(ctx *cli.Context) error {
								out := ctx.Args().First()
								markdown := ctx.Bool("markdown")
								cache := ctx.String("db")

								return p.Scraper.LoadAndOutputToDir(cache, out, markdown)
							},
						},
					},
				},
				{
					Name:  "ingest",
					Usage: "[file or directory]",
					Flags: []cli.Flag{
						&cli.StringFlag{
							Name:  "source",
							Usage: "Where the vulnerabilities have been sourced from.",
						},
						&cli.StringFlag{
							Name:  "source-relative-path",
							Usage: "Relative path from within the source to where advisories are located.",
						},
						&cli.StringFlag{
							Name:  "vuln-id",
							Usage: "ID of a vulnerability to make sure all related information is ingested (ex. affected packages).",
						},
						&cli.BoolFlag{
							Name:  "ingest-affected",
							Usage: "Ensure for every affected package that all metadata is collected.",
						},
					},
					Subcommands: []*cli.Command{},
					Action: func(ctx *cli.Context) error {
						advisoryLocation := ctx.Args().First()

						source := ctx.String("source")
						vulnID := ctx.String("vuln-id")
						sourceRelativePath := ctx.String("source-relative-path")
						ingestAffected := ctx.Bool("ingest-affected")

						if vulnID != "" {
							log.Info().
								Str("vulnerability", vulnID).
								Msg("processing vulnerability")
							return p.AffectedIngester.Ingest(ctx.Context, vulnID)
						}

						log.Info().
							Str("source", source).
							Msg("starting vulnerability ingestion")
						insertedVulns, err := p.Ingester.IngestVulnerabilitiesFromSource(advisoryLocation, source, sourceRelativePath)
						if err != nil {
							log.Error().
								Err(err).
								Str("source", source).
								Msg("failed to ingest vulnerabilities")
							return err
						}

						if ingestAffected {
							for _, vuln := range insertedVulns {
								log.Info().
									Str("vulnerability", vuln).
									Msg("processing vulnerability")
								err = p.AffectedIngester.Ingest(ctx.Context, vuln)
								if err != nil {
									continue
								}
							}
						}
						return nil
					},
				},
			},
		},
	}
}
